# -*- coding: utf-8 -*-
import random
import string
import logging
import urllib
import hashlib
import os

from google.appengine.ext import db
from google.appengine.api import images, mail

from django.forms.fields import email_re
from django.conf import settings

from adrianremembersmeapp.models import Member, LoginRecord, Photo, PasswordResetLink
from adrianremembersmeapp import armsettings

#This class will hold the functionality of the project
class AdrianRemembersMe():
    #Define error messages
    ERROR_OK, ERROR_USERNAME_NONE, ERROR_USERNAME_TAKEN, ERROR_PASSWORD_WEAK, ERROR_EMAIL_USED, ERROR_EMAIL_BAD, ERROR_LOGIN_BAD = range(0, 7)
    
    #Define Defaults
    minimum_password_length = 3
    
    def logincookieclear(self, cookieval):
        q = db.GqlQuery("SELECT * FROM LoginRecord WHERE code = :1", cookieval)
        logins = q.fetch(10) # todo revisit this 10 later
        for login in logins:
            login.delete()

    def loginclearmember(self, member, cookieval):
        loginrecords = db.GqlQuery("SELECT * FROM LoginRecord WHERE member = :1", member)
        for loginrecord in loginrecords:
            if loginrecord.code != cookieval:
                loginrecord.delete()

        
    def logingetlist(self, amount):
        return db.GqlQuery("SELECT * FROM LoginRecord").fetch(amount) # todo revisit this 50 later
    
    def memberlogin(self, response, membername, password, rememberme):
        member = db.GqlQuery("SELECT * FROM Member WHERE name = :1 LIMIT 1", membername).get()

        if member:
            if self.validatepassword(member,password):
                # ok, set cookie
                cookieval = self.generaterandom(128)
            
                # store in db
                loginrec = LoginRecord()
                loginrec.code = cookieval
                loginrec.member = member
                loginrec.put()
                
                if rememberme:
                    response.set_cookie('ack', urllib.quote(cookieval), expires="Fri, 31-Dec-2030 23:59:59 GMT")
                else:
                    response.set_cookie('ack', urllib.quote(cookieval), max_age=None)
                
                return True
            else:
                return False
                # todo processing for security
        
        return False

    def membergetloggedin(self, cookieval):
        # check if login
        loginrecord = db.GqlQuery("SELECT * FROM LoginRecord WHERE code = :1 LIMIT 1", cookieval).get()
        if loginrecord:
            return loginrecord.member
        #do some security logging for failed cookie
        return None
    
    def membercreate(self, membername, password, fullname, email, photofile=None, photoname=None):
        if (membername):
            mem = db.GqlQuery("SELECT * FROM Member WHERE name = :1 LIMIT 1", membername)
            if mem.count():
                return self.ERROR_USERNAME_TAKEN
            else:
                if len(password) >= self.minimum_password_length:
                    if armsettings.ENCRYPTED_PASSWORDS:
                        salt = self.generaterandom()
                        password = self.generatehash(password,salt)
                    # todo improve email checking algorithm
                    if email_re.match(email):
                        # check if email has been used already
                        # eventually set it up so users can have multiple email addresses (or abstract out email part)
                        mem = db.GqlQuery("SELECT * FROM Member WHERE email = :1 LIMIT 1", email)
                        if mem.count():
                            return self.ERROR_EMAIL_USED
                        else:
                            member = Member()
                            member.name = membername
                            member.salt = salt
                            member.password = password
                            member.fullname = fullname
                            member.email = email
                            member.put()
                            
                            if photofile != None:
                                photo = Photo()
                                photo.member = member
                                photo.filename = photoname
                                photo.content = photofile
                                photo.thumb = images.resize(photo.content, 32, 32)
                                photo.put()
                            
                            return self.ERROR_OK
                    else:
                        return self.ERROR_EMAIL_BAD
                else:
                    return self.ERROR_PASSWORD_WEAK
        else:
            return self.ERROR_USERNAME_NONE
        
    def memberchangepassword(self, member, newpassword):
        # update pwd
        if armsettings.ENCRYPTED_PASSWORDS:
            salt = member.salt
            if not salt:
                salt = self.generaterandom()
                member.salt = salt
            newpassword = self.generatehash(newpassword,salt)
        member.password = newpassword
        member.put()
        
        #send email to user
        if member.email:
            mail.send_mail(sender=settings.DEFAULT_FROM_EMAIL,
                        to=member.email,
                        subject="Password Changed",
                        body="""
                        If this was not approved by you, now would be a good time to panic.
                        """)
 
        return self.ERROR_OK
    
    def memberchangephoto(self, member, newphoto, newphotoname):
        photo = Photo()
        currentphoto = db.GqlQuery("SELECT * FROM Photo WHERE member = :1 LIMIT 1", member.key()).get()
        
        if currentphoto:
            photo = currentphoto
        else:
            photo.member = member.key()
            
        photo.content = newphoto
        photo.filename = newphotoname
        photo.save()
            

    def memberget(self, membername):
        return db.GqlQuery("SELECT * FROM Member WHERE name = :1 LIMIT 1", membername).get()
    
    def membergetlist(self):
        return db.GqlQuery("SELECT * FROM Member")
    
    def emailisavailable(self, email):
        mem = db.GqlQuery("SELECT * FROM Member WHERE email = :1 LIMIT 1", email).get()
        
        return mem == None
    
    def emailgetvalidationcode(self, member):
        member.emailvalidationcode = ''.join([random.choice(string.letters + string.digits) for x in xrange(25)])
        member.save()
        
        return member.emailcode
    
    def forgotpassword(self,memberemail,resetpath):
        member = db.GqlQuery("SELECT * FROM Member WHERE email = :1 LIMIT 1", memberemail).get()
        
        if member:
            if armsettings.ENCRYPTED_PASSWORDS:
                code = self.generaterandom(128)
                resetlink = PasswordResetLink()
                resetlink.member = member
                resetlink.code = code
                resetlink.alreadyused = False
                resetlink.put()
                url = os.path.join(resetpath, code + '/')
                body = """
To initiate the password reset process for your account, click the link below:

%s

If clicking the link above doesn't work, please copy and paste the URL in a new browser window instead.""" % url
            else:
                body = """
Here are the details for your account
username: %s
password: %s
                """ % (member.name, member.password)
            #send email to member
            sender_address = settings.DEFAULT_FROM_EMAIL
            user_address = memberemail
            subject = "Instructions for Password Reset"
            mail.send_mail(sender_address, user_address, subject, body)
            return True
        #do some security logging for invalid email on resetpassword
        return False
    
    def getresetlinkfromcode(self,code):
        resetlink = db.GqlQuery("SELECT * FROM PasswordResetLink WHERE code = :1 AND alreadyused = :2 LIMIT 1", code,False).get()
        if resetlink:
            #Calculate what is the earliest valid date (links are only valid for 24 hours)
            import datetime
            now = datetime.datetime.now()
            earliestvaliddate = now - datetime.timedelta(hours=24)
            if resetlink.timeadded > earliestvaliddate:
                return resetlink
            else:
                #Mark link as already used so it won't appear as valid anymore
                #Another option is to delete it from datastore
                resetlink.alreadyused = True
                resetlink.put()
        #todo: security logging for invalid password reset code
        return None
    
    def resetpassword(self,resetlink,newpassword):
        #todo: increase password validation
        if newpassword == '':
            return False
        resetlink.alreadyused = True
        resetlink.put()
        self.memberchangepassword(resetlink.member,newpassword)
        return True
    
    def validatepassword(self,member,password):
        if armsettings.ENCRYPTED_PASSWORDS:
            password = self.generatehash(password,member.salt)
        return password == member.password
    
    def generaterandom(self,saltlength=25):
        salt = ''.join([random.choice(string.letters + string.digits) for x in xrange(saltlength)])
        return salt
    
    def generatehash(self,originalvalue,prefixsalt='',postfixsalt=''):
        pre = str(prefixsalt)
        post = str(postfixsalt)
        value = pre + originalvalue + post      
        h = hashlib.sha512()
        h.update(value)
        return h.hexdigest()
    
    def encryptpasswords(self):
        if not armsettings.ENCRYPTED_PASSWORDS:
            return False
        query = Member.all()
        for member in query:
            if not member.salt or len(member.password) < 128:
                salt = self.generaterandom()
                password = self.generatehash(member.password,salt)
                member.salt = salt
                member.password = password
                member.put()
        return True
